package com.example.springpethospital.service;

import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.StrUtil;
import com.amazonaws.HttpMethod;
import com.amazonaws.services.s3.AmazonS3;
import com.amazonaws.services.s3.model.CompleteMultipartUploadRequest;
import com.amazonaws.services.s3.model.CompleteMultipartUploadResult;
import com.amazonaws.services.s3.model.GeneratePresignedUrlRequest;
import com.amazonaws.services.s3.model.InitiateMultipartUploadRequest;
import com.amazonaws.services.s3.model.InitiateMultipartUploadResult;
import com.amazonaws.services.s3.model.ListPartsRequest;
import com.amazonaws.services.s3.model.ObjectMetadata;
import com.amazonaws.services.s3.model.PartETag;
import com.amazonaws.services.s3.model.PartListing;
import com.amazonaws.services.s3.model.PartSummary;
import com.example.springpethospital.common.exception.PetHospitalException;
import com.example.springpethospital.config.MinioConfig;
import com.example.springpethospital.entity.file.dto.UploadTaskCreateDTO;
import com.example.springpethospital.entity.file.dto.UploadTaskDTO;
import com.example.springpethospital.entity.file.param.UploadTaskCreateParam;
import com.example.springpethospital.entity.file.po.UploadTaskPO;
import com.example.springpethospital.mapper.UploadTaskMapper;
import java.net.URL;
import java.util.Date;
import java.util.List;
import java.util.stream.Collectors;
import javax.annotation.Resource;
import org.springframework.http.MediaType;
import org.springframework.http.MediaTypeFactory;
import org.springframework.stereotype.Service;

@Service
public class FileService {

    @Resource
    private UploadTaskMapper uploadTaskMapper;
    @Resource
    private AmazonS3 amazonS3;
    @Resource
    private MinioConfig minioConfig;

    private static int PRE_SIGN_URL_EXPIRE = 60 * 10 * 1000;

    /**
     * 创建一个上传任务
     */
    public UploadTaskCreateDTO createUploadTask(UploadTaskCreateParam param) {
        UploadTaskPO uploadTaskPO = uploadTaskMapper.queryByIdentifier(param.getIdentifier());
        if (uploadTaskPO != null) {
            throw new PetHospitalException("文件已存在, 请勿重复上传   ");
        }
        Date currentDate = new Date();
        String bucketName = minioConfig.getBucket();
        String fileName = param.getFileName();
        String suffix = fileName.substring(fileName.lastIndexOf(".") + 1);
        String key = StrUtil.format("{}/{}.{}", DateUtil.format(currentDate, "YYYY-MM-dd"), IdUtil.randomUUID(), suffix);
        String contentType = MediaTypeFactory.getMediaType(key).orElse(MediaType.APPLICATION_OCTET_STREAM).toString();
        ObjectMetadata objectMetadata = new ObjectMetadata();
        objectMetadata.setContentType(contentType);
        InitiateMultipartUploadResult initiateMultipartUploadResult = amazonS3
            .initiateMultipartUpload(new InitiateMultipartUploadRequest(bucketName, key).withObjectMetadata(objectMetadata));
        String uploadId = initiateMultipartUploadResult.getUploadId();

        UploadTaskPO task = new UploadTaskPO();
        int chunkNum = (int) Math.ceil(param.getTotalSize() * 1.0 / param.getChunkSize());
        task.setBucketName(minioConfig.getBucket())
            .setChunkNum(chunkNum)
            .setChunkSize(param.getChunkSize())
            .setTotalSize(param.getTotalSize())
            .setFileIdentifier(param.getIdentifier())
            .setFileName(fileName)
            .setObjectKey(key)
            .setUploadId(uploadId);
        uploadTaskMapper.insert(task);
        return new UploadTaskCreateDTO().setFinished(false).setTaskRecord(new UploadTaskDTO(task)).setPath(getPath(bucketName, key));
    }

    /**
     * 获取上传进度
     */
    public UploadTaskDTO queryUploadSchedule(String identifier) {
        UploadTaskPO uploadTaskPO = uploadTaskMapper.queryByIdentifier(identifier);
        return uploadTaskPO == null ? null : new UploadTaskDTO(uploadTaskPO);
    }

    public String getPath(String bucket, String objectKey) {
        return StrUtil.format("{}/{}/{}", minioConfig.getEndpoint(), bucket, objectKey);
    }

    /**
     * 获取每个分片的预签名上传地址
     */
    public String getPerSignUploadUrl(String bucketName, String objectKey, String uploadId, Integer partNumber) {
        Date expireDate = DateUtil.offsetMillisecond(new Date(), PRE_SIGN_URL_EXPIRE);
        GeneratePresignedUrlRequest request = new GeneratePresignedUrlRequest(bucketName, objectKey)
            .withExpiration(expireDate).withMethod(HttpMethod.PUT);
        request.addRequestParameter("uploadId", uploadId);
        request.addRequestParameter("partNumber", String.valueOf(partNumber));
        URL preSignedUrl = amazonS3.generatePresignedUrl(request);
        return preSignedUrl.toString();
    }


    public CompleteMultipartUploadResult merge(String identifier) {
        UploadTaskPO task = uploadTaskMapper.queryByIdentifier(identifier);
        if (task == null) {
            throw new RuntimeException("分片任务不存在");
        }

        ListPartsRequest listPartsRequest = new ListPartsRequest(task.getBucketName(), task.getObjectKey(), task.getUploadId());
        PartListing partListing = amazonS3.listParts(listPartsRequest);
        List<PartSummary> parts = partListing.getParts();
        if (!task.getChunkNum().equals(parts.size())) {
            // 已上传分块数量与记录中的数量不对应，不能合并分块
            throw new RuntimeException("分片缺失，请重新上传");
        }
        CompleteMultipartUploadRequest completeMultipartUploadRequest = new CompleteMultipartUploadRequest()
            .withUploadId(task.getUploadId())
            .withKey(task.getObjectKey())
            .withBucketName(task.getBucketName())
            .withPartETags(parts.stream().map(partSummary -> new PartETag(partSummary.getPartNumber(), partSummary.getETag())).collect(Collectors.toList()));
        return amazonS3.completeMultipartUpload(completeMultipartUploadRequest);
    }
}
